from Clases.Memoria import Memoria
from Clases.Signal import Signal

class Paginacion ():
    
    def __init__(self,tamMarco):
        self.memoria = Memoria()
        self.tamanioMarco = tamMarco
        self.marcos = self.creacionDeMarcos(tamMarco) #key = marco value=celdas
        self.tablaPaginas = {} # key =(idProg,nroPag) value = Marco(none representa que no tiene asignado ningun marco y x lo tanto debe buscar las instrucciones en el disco)
        self.paginasProgramas = {} #Cada programa tiene una lista de paginas. (key = pid ,value = listaPaginas)
        self.marcoDeReferencia = 0;
        
    def creacionDeMarcos(self,tamMarco):
        #asigna los marcos a la memoria guardando en un diccionario
        marcos = {}
        nroMarcos = self.memoria.getMaxCelda() // tamMarco 
        celda = 0
        for k in range(nroMarcos):
            marcos[k] = celda #[inicioCelda, finCelda] de un marco k
            celda += tamMarco
        return marcos
#        
#    def obtenerNroPagina(self,pid,nroInstruccionActual):
#        return (nroInstruccionActual//self.tamanioMarco )#los valores resultantes son 0 , 1 , 2 y el id de las paginas comienza en 0,1,2... etc.

    def obtenerNroPagina(self,pid,nroInstruccionActual):
        nroPag = (nroInstruccionActual//self.tamanioMarco )#los valores resultantes son 0 , 1 , 2 y el id de las paginas comienza en 0,1,2... etc.
        # // dividir asi me da la division entera mientras los  2 numeros utilizados sean enteros.
        if not (nroPag == self.paginasProgramas [pid]): #si la pagina requerida no se encuentra en la lista de paginas BOOM!!!...
            ## le pasas un valor , in , y lista y te retorna un booleano si esta en la lista
            raise Exception('SE esta intentando acceder a una PAGINA NO EXISTENTE')
        return nroPag

    #cuando es paginacion el disco guarda pid -listaPaginas
    #retorna un diccionario {nroPag : listaInstruc} 
    def crearDatosParaDisco(self,pid,listaInstrucciones):
        instADisco = []
        dicc = {}
        pag = 0
        cantIns = 0
        for ins in listaInstrucciones:
            instADisco.append(ins)
            cantIns+=1
            if not self.tamanioMarco == cantIns:
                continue
            dicc[pag] = instADisco
            #Setea variables
            pag += 1
            cantIns = 0
            instADisco = []
        #procesa el ultimo elemento 
        if not instADisco == []:
            dicc[pag] = instADisco
        #guarda la id con sus cantidad de paginas    
        listPag = []
        for k,v in dicc.items():
            listPag.append(k)
        self.paginasProgramas[pid] = listPag
        return dicc

    def estaEnMemoria(self, pid, nroPag): #utilizo una tupla y no una lista de 2 elementos como el key del map
        if (pid,nroPag) in self.tablaPaginas.keys():
            return  not ((self.tablaPaginas [(pid,nroPag)])is None ) #obtengo el elemento,le pregunto si es None niego la condicion y entonces no esta en memoria.
            
        else:
            return False #retorno false xq el programa no esta cargando en la tabla
        ## con esta representacion no tenemos en la tablaPaginas todos los  pids con cada pagina , su valor xq se agrega el ir leyendo del disco. 
        ## solamente estarian en memoria las paginas que en algun momento fueron requeridas para la lectura

    def leerDeMemoria(self,pid, nroPag,instruccionActual):
        nroMarco = self.tablaPaginas[(pid,nroPag)]
        bloqueMemoriaAsignadoAMarco=self.marcos [nroMarco]
        return self.memoria.read(bloqueMemoriaAsignadoAMarco + instruccionActual) # chequear##  bloqueMemoriaAsignadoAMarco es una tupla [a,b]
                                                                            #obtengo la dirBase +desplazamiento
    def cantidadMarcosUsados(self):
        contador =  0
        for marco in self.tablaPaginas.values():
            if not marco is None:
                contador+=1
        return contador
    
    def obtenerMarcosLibres(self):
        listaOcupados = self.marcosOcupados()
        totalMarcos =self.marcos.keys() ## EERROR ACA X EL TIPO Q DEVUELVE KEYS LPM!!!
        
        pasarAlista = []# el fix que le hice xp se va a la q lo tiro de las patas
        for nro in totalMarcos:
            pasarAlista.append(nro)
        totalMarcos = pasarAlista
        
        for nroMarco in listaOcupados:
            totalMarcos.remove(nroMarco) # total marcos tiene todos los ints de marcos x lo q la operacion remove es total 
        return totalMarcos
    
    def marcosOcupados(self): #me retorna una lista con los nros de marcos ocupados
        ocupados=[]
        for marco in self.tablaPaginas.values():
            if not marco is None:
                ocupados.append(marco)
        return ocupados
    
    def hayMarcosLibresEnMemoria(self):#compara el total de marcos con la cantidad de marcos usados.
        return  len(self.marcos.keys()) > self.cantidadMarcosUsados() 
        
        #esta modelado para guardar ,leer o hace swap de una pagina
    def obtenerInstruccion(self,pcb,manager):
        #el manager es x si queremos enviar una interrupcion
        print(self.marcos)
        print("obtener instruccion")
        pid = pcb.getIdProgr()
        instruccionActual =pcb.getCurrentInst()
        nroPag = self.obtenerNroPagina(pid,instruccionActual)
        print(self.estaEnMemoria(pid, nroPag))
        if self.estaEnMemoria(pid,nroPag): 
            valorResultado =self.leerDeMemoria(pid,nroPag,instruccionActual)
            print("se leyo con exito la instruccion de la memoria")
            return valorResultado
        elif self.hayMarcosLibresEnMemoria(): #si hay marcos libres entonces cargar la pagina requerida a la memoria 
            print("condicion hay marcos libres en memoria")
            listaInstrucciones=self.buscarPaginaEnDisco(pid,nroPag,manager) #me deberia retornar una lista 2 elementos 
            print("voy a guardar un programa en memoria")
            self.guardaEnMemoria(pid,nroPag, listaInstrucciones) 
            return  self.obtenerInstruccion(pcb, manager)
        else:
            manager.interrupt(Signal.SWAP) ## TO DO
            return self.obtenerInstruccion(pcb, manager)
        
    def guardaEnMemoria(self,pid,nroPag,listaInstrucciones): # guarda una pagina solamente
        if (self.memoria.getMaxCelda() // self.tamanioMarco)-1 == self.marcoDeReferencia: #se utilizara para la asignacion de marcos FIFO
                self.marcoDeReferencia = 0
        else:
                self.marcoDeReferencia += 1
        #obtener 1 marco libre 
        nroMarcoLibre =self.obtenerMarcosLibres() [0] # como se que tengo a menos 1 marco la operacion es total (o deberia ser total -.-)
        #agregar en la tablaPaginas el marco utilizado nuevo
        self.tablaPaginas[(pid,nroPag)] = nroMarcoLibre  
        print("pid ",pid ,"nroPag ",nroPag,"nroMarcolibre " , nroMarcoLibre) 
        print("GUARDAR EN MEMORIA TABLA PAGINAs",self.tablaPaginas)
        marco= self.marcos[nroMarcoLibre] # esto me debe retornar una tupla q seniala el inicio y fin del bloque de memoria q corresponde a este marco
        #poblar el marco seleccionado
        print("LISTA DE INSTRUCCIONES en guardaEnMemoria" ,listaInstrucciones)
        self.memoria.loadBlock(listaInstrucciones, marco)
        
        
    def buscarPaginaEnDisco(self,pid,nroPag,manager): #retorna una lista de instrucciones para poner en un marco libre
        disco=manager.getKernel().getDisco()
        print("BUSCAR PAGINA NRO PAG",nroPag)
        print("haber q ahi en disco ", disco.obtenerPrograma(pid))
        v=disco.obtenerPrograma(pid).pop(nroPag) 
        print("BUSCAR PAGINA el resultado",v)
        return v #{nroPag : listaInstruc , nroPag2 : listaInstruc2 }

        
    def guardaEnDisco(self,kernel):# aca se debe hacer un swap  #mensaje enviado x el MMU al hacer swap
        disco = kernel.getDisco()
        #preparar variables
        idProg = 0
        key = None
        #buscar la pag del programa donde se encuentra el marco
        for k,v in self.tablaPaginas.items():
            if v == self.marcoDeReferencia: # si el marco es igual a la referencia
                #tomo el id del programa y el nro de pagina
                idProg = k[0]
                key = k
          
        print("EN GUARDAR EN DISCO tablaPaginas",self.tablaPaginas)       
        self.tablaPaginas[key] = None #seteo a None ya que se borrara de memoria        
        listInstrucciones = []
        dirFisMarco = self.marcos[self.marcoDeReferencia]
        for i in range(self.tamanioMarco):
            listInstrucciones.append(self.memoria.read(dirFisMarco))
            dirFisMarco+=1
            print("un indice ",i)
        print("EN GUARDAR EN DISCO listInstrucciones ",listInstrucciones)
        print("estado inicial del DISCO ",disco.obtenerPrograma(idProg))
        disco.obtenerPrograma(idProg)[key[1]] = listInstrucciones ## esto modifica directamente al diccionario.
        print("EL DATO Q VOY A GUARDAR EN EL DISCO RIGIDO EN EL METODO  GUARDAR EN DISCO",disco.obtenerPrograma(idProg))
        
       
    def eliminarProgramaDeMemoria(self,pid):
        listaPaginas = self.paginasProgramas.pop(pid)
        listaMarcos = []
        #obtener los marcos que tiene guardado el programa
        for pag in listaPaginas:
            resultado =self.tablaPaginas.pop((pid,pag))
            if not  resultado is None:
                listaMarcos.append(resultado)
            
        for marco in listaMarcos:
            self.memoria.unloadBlock(self.marcos[marco], self.tamanioMarco)